/*
 * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.svm.core.nodes.foreign;

import static jdk.graal.compiler.nodeinfo.InputType.Memory;

import java.lang.reflect.Field;

import org.graalvm.collections.EconomicSet;
import org.graalvm.word.LocationIdentity;

import com.oracle.svm.core.nodes.ClusterNode;
import com.oracle.svm.util.ReflectionUtil;

import jdk.graal.compiler.core.common.type.StampFactory;
import jdk.graal.compiler.graph.Node;
import jdk.graal.compiler.graph.NodeClass;
import jdk.graal.compiler.graph.NodeInputList;
import jdk.graal.compiler.nodeinfo.NodeCycles;
import jdk.graal.compiler.nodeinfo.NodeInfo;
import jdk.graal.compiler.nodeinfo.NodeSize;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.FixedNode;
import jdk.graal.compiler.nodes.FixedWithNextNode;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.debug.ControlFlowAnchored;
import jdk.graal.compiler.nodes.memory.MemoryAccess;
import jdk.graal.compiler.nodes.memory.MemoryKill;
import jdk.graal.compiler.nodes.spi.Simplifiable;
import jdk.graal.compiler.nodes.spi.SimplifierTool;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.misc.ScopedMemoryAccess;

/**
 * See {@link ClusterNode} for details.
 *
 * Mark the beginning of a {@link ScopedMemoryAccess} checking the validity of a memory session.
 */
@NodeInfo(cycles = NodeCycles.CYCLES_UNKNOWN, size = NodeSize.SIZE_UNKNOWN)
public class MemoryArenaValidInScopeNode extends FixedWithNextNode implements MemoryAccess, ControlFlowAnchored, Simplifiable {
    public static final Field STATE_FIELD = ReflectionUtil.lookupField(MemorySessionImpl.class, "state");

    public static final NodeClass<MemoryArenaValidInScopeNode> TYPE = NodeClass.create(MemoryArenaValidInScopeNode.class);
    @Node.OptionalInput ValueNode value;
    private final LocationIdentity fieldLocation;
    @OptionalInput(Memory) MemoryKill lastLocationAccess;

    public MemoryArenaValidInScopeNode(ValueNode value, LocationIdentity fieldLocation) {
        super(TYPE, StampFactory.forInteger(64));
        this.value = value;
        this.fieldLocation = fieldLocation;
    }

    @OptionalInput NodeInputList<ValueNode> scopeAssociatedValues;

    public void addScopeAssociatedValue(ValueNode memorySession) {
        if (this.scopeAssociatedValues == null) {
            this.scopeAssociatedValues = new NodeInputList<>(this);
        }
        this.scopeAssociatedValues.add(memorySession);
    }

    public ValueNode getValue() {
        return value;
    }

    public void delete(int val) {
        this.replaceAtUsages(ConstantNode.forLong(val, graph()));
        this.graph().removeFixed(this);
    }

    @Override
    public MemoryKill getLastLocationAccess() {
        return lastLocationAccess;
    }

    @Override
    public void setLastLocationAccess(MemoryKill lla) {
        updateUsagesInterface(lastLocationAccess, lla);
        lastLocationAccess = lla;
    }

    @Override
    public LocationIdentity getLocationIdentity() {
        return fieldLocation;
    }

    @Override
    public void simplify(SimplifierTool tool) {
        if (tool.allUsagesAvailable() && graph() != null) {
            final ValueNode session = value;
            if (session.isNullConstant()) {
                FixedNode predecessor = (FixedNode) this.predecessor();

                EconomicSet<ScopedMethodNode> scopeNodes = EconomicSet.create();
                // also cleanup any other nodes that check for inlined callees
                // this is a best effort
                FixedNode cur = predecessor;
                while (cur != null) {
                    if (cur instanceof ScopedMethodNode scopeNode) {
                        // scope nodes are always balanced, find the next start, that must be ours,
                        // if we find an end we do not delete our pair
                        if (scopeNode.getType() == ScopedMethodNode.Type.START) {
                            scopeNodes.add(scopeNode);
                            for (Node usage : scopeNode.usages()) {
                                if (usage instanceof ScopedMethodNode scopeUsage && scopeUsage.getType() == ScopedMethodNode.Type.END && scopeUsage.getStart() == scopeNode) {
                                    scopeNodes.add(scopeUsage);
                                }
                            }
                            // we collected the usages, check that we found all of them
                            for (Node usage : scopeNode.usages()) {
                                if (!(usage instanceof ScopedMethodNode sm && scopeNodes.contains(sm))) {
                                    return;
                                }
                            }
                            break;
                        } else {
                            return;
                        }
                    }
                    cur = (FixedNode) cur.predecessor();
                }

                if (scopeNodes.isEmpty()) {
                    /*
                     * Collect and delete all scope nodes in a single batch to ensure they are
                     * optimized together. If we don't do this, the current node might be optimized
                     * away before the scope nodes are inlined, resulting in incomplete
                     * optimization. We wait until both are present in the graph before optimizing.
                     */
                    return;
                }

                // delete and replace the scope node first
                this.delete(0);

                // clear all the scope nodes
                for (ScopedMethodNode scopeNode : scopeNodes) {
                    scopeNode.delete();
                }
            }
        }
    }
}
